package com.daikun.common.aop.handler;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.util.DesensitizedUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSON;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.daikun.common.aop.annos.BaseLog;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;

import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * 日志解析器
 * 会对注解方法进行环绕切面，在方法执行结束后，会打印方法的入参，出参，方法执行时长
 * tips -> 方法入参在方法内部被修改后，此处打印的还是原始入参，不会因为方法内部的修改而改变
 * @author earl created 2025-07-07
 * @version init 0.0.1
 */
@Slf4j
@Aspect
@Component
public class BaseLogHandler {

    @Pointcut("@annotation(com.daikun.common.aop.annos.BaseLog)")
    public void invoke() {}

    @Around("invoke()")
    public Object pointCut(ProceedingJoinPoint point) {
        long startTime = System.currentTimeMillis();
        MethodSignature target = (MethodSignature) point.getSignature();

        BaseLog baseLog = AnnotationUtils.getAnnotation(target.getMethod(), BaseLog.class);
        if (baseLog == null) {
            try {
                return point.proceed();
            } catch (Throwable e) {
                log.info("BaseLogParser.pointCut info 接口执行异常 -> {}", e.getMessage(), e);
                throw new RuntimeException(e);
            }
        }
        String logKey = StrUtil.isBlank(baseLog.value()) ? (target.getDeclaringTypeName() + "#" + target.getName()) : baseLog.value();


        Object[] args = point.getArgs();

        try {
            SubHandler subHandler = null;
            String request = JSONUtil.toJsonStr(args);
            if (StrUtil.isNotBlank(request) && StrUtil.isNotBlank(baseLog.reqRemark())) {
                request = remark(JSONUtil.parseArray(request), StrUtil.split(baseLog.reqRemark(), ",", true, true)).toString();
            }

            if (request != null && baseLog.reqSub() && request.length() > baseLog.reqSubLimit()) {
                if (baseLog.subHandler() != SubHandler.class) {
                    subHandler = ReflectUtil.newInstance(baseLog.subHandler());
                    request = subHandler.subRequest(request);
                } else {
                    request = request.substring(0, baseLog.reqSubLimit()) + " ...... ";
                }
            }

            Object result = point.proceed();

            String response = JSONUtil.toJsonStr(result);
            if (StrUtil.isNotBlank(response) && JSONUtil.isTypeJSON(response) && StrUtil.isNotBlank(baseLog.resRemark())) {
                JSON json = JSONUtil.isTypeJSONArray(response) ? JSONUtil.parseArray(response) : JSONUtil.parseObj(response);
                response = remark(json, StrUtil.split(baseLog.resRemark(), ",", true, true)).toString();
            }
            if (StrUtil.isNotBlank(response) && baseLog.resSub() && response.length() > baseLog.resSubLimit()) {
                if (subHandler != null) {
                    response = subHandler.subResponse(response);
                } else {
                    response = response.substring(0, baseLog.resSubLimit()) + " ...... ";
                }
            }
            log.info("{} info -> request = {}, response = {}, useTime = {}", logKey, request, response, System.currentTimeMillis() - startTime);
            return result;
        } catch (Throwable e) {
            log.info("{} invoke err -> msg = {}, useTime = {}", logKey, e.getMessage(), System.currentTimeMillis() - startTime, e);
            if (baseLog.isThrow())
                throw new RuntimeException(e);
            return null;
        }
    }

    private static JSON remark(JSON data, List<String> remarkKeys) {
        if (data == null) {
            return null;
        }
        if (CollUtil.isEmpty(remarkKeys)) {
            return data;
        }
        if (JSONUtil.isTypeJSONObject(data.toString())) {
            JSONObject obj = (JSONObject) data;

            for (Map.Entry<String, Object> entry : obj) {
                if (remarkKeys.contains(entry.getKey())) {
                    obj.set(entry.getKey(), hide(entry.getValue() == null ? "*******" : entry.getValue().toString()));
                } else if (entry.getValue() != null && JSONUtil.isTypeJSON(entry.getValue().toString())) {
                    remark(JSONUtil.isTypeJSONArray(entry.getValue().toString()) ? (JSONArray) entry.getValue()
                            : (JSONObject) entry.getValue(), remarkKeys);
                }
            }
        } else {
            JSONArray arr = (JSONArray) data;
            for (Object o : arr) {
                if (o != null && JSONUtil.isTypeJSON(o.toString())) {
                    remark(JSONUtil.isTypeJSONObject(o.toString()) ? (JSONObject) o : (JSONArray) o,  remarkKeys);
                }
            }
        }
        return data;
    }

    private static String hide(String os) {
        if (os == null) {
            return null;
        }
        if (os.length() < 5) {
            return "*******";
        }
        if (os.length() < 7) {
            return os.charAt(0) + "*****" + os.substring(os.length() - 1);
        }
        if (os.length() < 9) {
            return os.charAt(0) + os.charAt(1) + "***" + os.substring(os.length() - 2);
        }
        return os.substring(0, 3) + "*" + os.substring(os.length() - 3);
    }

    /**
     * 此接口的子类必须存在无参构造方法，否则程序会出异常
     */
    public interface SubHandler {

        String subRequest(String request);

        String subResponse(String response);

    }

}
